package org.ccay.excel.exporter.service.impl.internal;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.ClassUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.ccay.core.base.PageConfig;
import org.ccay.core.base.PageVO;
import org.ccay.core.exception.ApplicationException;
import org.ccay.core.ioc.Ccay;
import org.ccay.core.log.CcayLoggerFactory;
import org.ccay.core.log.ILogger;
import org.ccay.core.orm.PreparedStatementParams;
import org.ccay.core.util.CollectionUtil;
import org.ccay.core.util.FileUtil;
import org.ccay.core.util.StringUtil;
import org.ccay.core.util.exception.BadArgException;
import org.ccay.excel.exception.ExcelExportConstants;
import org.ccay.excel.exception.ExcelExportException;
import org.ccay.excel.exception.ExcelSystemException;
import org.ccay.excel.exporter.ExportTaskRequest;
import org.ccay.excel.exporter.IExcelDataProvider;
import org.ccay.excel.exporter.IExcelExportContext;
import org.ccay.excel.exporter.IExcelExportSupport;
import org.ccay.excel.exporter.IExcelExtentionDataProvider;
import org.ccay.excel.exporter.IFieldProcessor;
import org.ccay.excel.importer.service.impl.internal.ConvertException;
import org.ccay.excel.importer.service.impl.internal.IVOConverter;
import org.ccay.excel.importer.service.impl.internal.VOConverterByBeanUtils;
import org.ccay.excel.meta.ColumnMeta;
import org.ccay.excel.meta.ExcelMeta;
import org.ccay.excel.meta.IDynamicExcelMetaProvider;
import org.ccay.excel.meta.SheetMeta;
import org.ccay.excel.util.impl.ExcelUtil;
import org.ccay.registry.util.RegistryQueryUtil;
import org.springframework.util.ReflectionUtils;

/**
 * excel导出处理类
 * 
 * @author chaowangbang
 * 
 */
public class ExcelStreamWriter {

	private static final ILogger log = CcayLoggerFactory.getLogger(ExcelStreamWriter.class);
	
	// 配置的Excel Sheet的元数据信息
	private SheetMeta sheetMeta;

	// vo转化器对象
	private IVOConverter voConverter;

	// 查询条件
	private Serializable conditionObject;

	private int batchSize = 0;

	private Map<Integer, String> dataType;
	
	private Map<String,IFieldProcessor> fieldProcessor;

	private String[] fieldNames;

	private String[] excelTitles;
	private String[] excelAnnotations;

	private PoiStreamWriter reportWriter = null;


	// 统计已导出的行数
	private int totalNum = 0;

	// 从数据字典中取出的最大总数
	private Long maxTotalNum;
	
	
	private IExcelExportContext context;

	
	public ExcelStreamWriter(IExcelExportContext context) {
		this.context=context;
	}
	

	/**
	 * 写入生成excel过程
	 * @author chaowangbang
	 * @since 2013-6-17
	 * @param taskRequest
	 * @throws ApplicationException
	 */
	public IExcelExportContext writer() throws ApplicationException{
		int totalCount = 0;
		
		//获取本次请求
		ExportTaskRequest taskRequest=this.context.getRequest();
		//生成文件路径和文件名(服务器上)
		String fileStore  = ExcelUtil.buildExportFileStore(taskRequest.getExcelType());
		//基于规则获取 IExcelExportSupport. IExcelExportSupport 为扫描到的Bean
		IExcelExportSupport exportSupport  = getExportSupport(taskRequest.getExcelType());
		
		//初始化excel参数值
		excelInit(taskRequest,fileStore);
		//获取Excel元数据
		ExcelMeta excelMeta = exportSupport.getExcelMeta();
		//动态处理模板
		excelMeta = processDynamicProvider(excelMeta);
		this.context.setExcelMeta(excelMeta);
		exportSupport.setExcelMeta(excelMeta);
		
		List<SheetMeta> sheetList = excelMeta.getSheetList();
		if(null == sheetList || sheetList.isEmpty()){
			//没有找到sheet元素
			log.debug("sheetList no data.");
			throw new ExcelExportException(ExcelExportConstants.SHEETS_NOT_FOUND);
		}
		
		//设置任务导出的文件名称
		taskRequest.setFileName(getFileName(excelMeta.getFileName(),taskRequest.getExcelType()));
		//给request对象设置文件地址
		taskRequest.setFileStore(fileStore);
		
		Object obj=null;
		
		for(SheetMeta sheetMeta : sheetList){
	
			obj=exportSupport.getDataProvider(sheetMeta.getSheetName());
			
			//判断是否实现了导出扩展类
			if(obj instanceof IExcelExtentionDataProvider){
				IExcelExtentionDataProvider excelDataProvider=(IExcelExtentionDataProvider)obj;
				try{
					//扩展开始执行的动作
					this.context.setCurrentSheet(sheetMeta);
					excelDataProvider.begin(this.context);
					
					log.debug("Before process sheet {0}", sheetMeta.getSheetName());
					// 此处根据 exportSupport 中拿到的 IDataProvider 获取数据，并使用 POI3.8的流方式输出Excel。（写在单独的类中）
					totalCount = totalCount + writeSheetDataToExcel(this.context.getCurrentSheet(), (IExcelDataProvider)obj,exportSupport);
					
					//扩展结束执行的动作
					excelDataProvider.end(this.context);
				}catch(ExcelExportException eex){
					//处理异常
					log.error(eex);
					excelDataProvider.fail(this.context);
					throw eex;
				}catch(RuntimeException rex){
					//处理异常
					log.error(rex);
					excelDataProvider.fail(this.context);
					throw rex;
				}catch(Exception ex){
					//处理异常
					log.error(ex);
					excelDataProvider.fail(this.context);
				}catch(Error er){
					//处理异常
					log.error(er);
					excelDataProvider.fail(this.context);
					throw er;
				}
			}else{
				log.debug("Before process sheet {0}", sheetMeta.getSheetName());
				// 此处根据 exportSupport 中拿到的 IDataProvider 获取数据，并使用 POI3.8的流方式输出Excel。（写在单独的类中）
				totalCount = totalCount + writeSheetDataToExcel(sheetMeta, exportSupport.getDataProvider(sheetMeta.getSheetName()),exportSupport);
			}
		}
		//生成文件
		excelExportFile();
		//设置导出的总数数据
		taskRequest.setTotalCount(totalCount);
		taskRequest.setFileSize(getFileSize());
		return this.context;
	}
	
	/**
	 * 初始化excel参数	
	 * @author chaowangbang
	 * @since 2013-6-19
	 * @param taskRequest
	 * @param fileStore
	 */
	private void excelInit(ExportTaskRequest taskRequest,String fileStore){
		/*初始化生成excel值*/
		this.voConverter = new VOConverterByBeanUtils();
		// 创建写报表对象	
		reportWriter = new PoiStreamWriter(fileStore);
		this.conditionObject = taskRequest.getConditionObject();
		this.maxTotalNum = RegistryQueryUtil.getLongValue("Ccay.Excel.ExcelExportMaxCount");
		reportWriter.setUserParameters(this.context.getRequest().getParameters());
		
	}
	
	/**
	 * 得到文件名	
	 * @author chaowangbang
	 * @since 2013-6-17
	 * @param fileName
	 * @param excelType
	 * @return
	 */
	private String getFileName(String fileName,String excelType){
		//设置生成的文件名
		String name = null;
		//优先执行用户设置的文件名称
		if(this.context.getRequest().getParameters()!= null 
				&& this.context.getRequest().getParameters().containsKey("dynamicFileName")){
			name = ExcelUtil.buildExportFileName(this.context.getRequest().getParameters().get("dynamicFileName").toString());
		}else if(StringUtil.isNullOrEmpty(fileName)){
			name = ExcelUtil.buildExportFileName(StringUtil.firstUpperCase(StringUtil.subString4lastIndex(excelType, ".")));
		}else{
			name = ExcelUtil.buildExportFileName(fileName);
		}
		return FileUtil.validateFileName(name);
	}
	
	/** 
	 * 获取导出支持Bean
	 * @param excelType
	 * @return
	 * @throws ApplicationException 
	 */
	private IExcelExportSupport getExportSupport(String excelType) throws ApplicationException {
		return ExcelUtil.getExportSupport(excelType);
	}
	
	/**
	 * 动态处理Excel模板，如果处理过后返回的模板为空则抛异常
	 * @param excelMeta Excel原始模板
	 * @return 动态处理后的模板
	 * @throws ApplicationException	
	 * @author chaowangbang
	 * @since 2014-9-22
	 */
	private ExcelMeta processDynamicProvider(ExcelMeta excelMeta) throws ApplicationException{
		// 获取模板动态处理Bean名字
		ExcelMeta copyExcelMeta = excelMeta;
		String dynamicProvider = excelMeta.getDynamicProvider();
		if(StringUtil.isNullOrEmpty(dynamicProvider)){
			return excelMeta;
		}
		IDynamicExcelMetaProvider dynamicProviderBean = Ccay.getContext().getBean(dynamicProvider, IDynamicExcelMetaProvider.class);
		try {
			copyExcelMeta = excelMeta.copy();
		} catch (CloneNotSupportedException e) {
			log.error(e);
		}
		// 转换参数类型
		Map<String, String> userParamMap = new HashMap<String,String>();
		Map<String, Object> parameters = context.getRequest().getParameters();
		if(!CollectionUtil.isNullOrEmpty(parameters)){
			Set<Entry<String, Object>> entrySet = parameters.entrySet();
			for(Map.Entry<String, Object> entry : entrySet){
				userParamMap.put(entry.getKey(),entry.getValue().toString());
			}
		}
		// 获取动态处理后的模板
		copyExcelMeta = dynamicProviderBean.process(copyExcelMeta, userParamMap);
		// 如果处理过后返回的模板为空则抛异常
		if(copyExcelMeta == null){
			log.error("Cannot find excel meta after process dynamic provider : " + dynamicProvider);
			throw new ExcelSystemException("ccay.excel.00060025"
					, new NullPointerException("ExcelMeta is null"));
		}
		return copyExcelMeta;
	}
	
	/**
	 * 写单sheeet的数据到excel
	 * 
	 * @param sheetMeta
	 * @param dataProvider
	 * @throws ApplicationException
	 */
	public int writeSheetDataToExcel(SheetMeta sheetMeta, IExcelDataProvider dataProvider,IExcelExportSupport exportSupport) throws ApplicationException {
		if (sheetMeta == null || CollectionUtil.isNullOrEmpty(sheetMeta.getColumns())) {
			log.debug("sheetMeta no data.");
			throw new ExcelExportException(ExcelExportConstants.SHEET_NOT_FOUND);
		}
		// sheet对象
		this.sheetMeta = sheetMeta;

		// 设置导出的betchsize
		this.batchSize = sheetMeta.getBatchSize();
		// 如果没有设置 那么使用默认值
		if (this.batchSize <= 0) {
			this.batchSize =  RegistryQueryUtil.getIntegerValue(ExcelExportConstants.EXPORT_BATCH_SIZE_PATH);
		}
		/**
		 * 如果配置的bachsize大于 限制最大值 那么取最大值
		 */
		if(batchSize > PageConfig.DEFAULT.getMaxPageSize()){
			batchSize = PageConfig.DEFAULT.getMaxPageSize();
		}
		log.debug("this.batchSize = " + this.batchSize);
		// 解析sheet元素XML对象
		initColumnsConverter(exportSupport);

		reportWriter.creatSheet(this.sheetMeta.getDisplayName(),excelTitles,excelAnnotations,this.sheetMeta);
		reportWriter.writeTitleData(sheetMeta.getTitleRowIndex());
		reportWriter.setDataType(this.dataType);
		
		
		int dataRowIndex = this.sheetMeta.getDataRowIndex();
		
		PageVO pageVO = this.context.getRequest().getPageObject();
		
		if(null == pageVO){
			pageVO = new PageVO();
		}
		// 获取数据
		pageVO.setCurPage(1);
		pageVO.setPageSize(batchSize);
		
		// 查询第一批分页数据
		List<?> batchData = dataProvider.getBatchData(this.conditionObject, pageVO);
		//判断当前处理逻辑是否是ResultSet
		if(checkPreparedStatementParams(batchData)){
			//使用resoutset 写数据给excel
			return writeCellDataByResultSet(dataRowIndex,(PreparedStatementParams) batchData.get(0));
		}
		// 这里加一个判断要导出的总行数是否超过最大导出数
		checkMaxCount(pageVO.getTotalRows());
		return writeCellDataByList(dataRowIndex,pageVO,batchData,dataProvider);
	}
	/**
	 * 导出数据
	 * @param dataRowIndex
	 * @param rs
	 * @return
	 * @throws ApplicationException
	 * Created：2013-3-20
	 * @author chaowangbang
	 */
	private int writeCellDataByResultSet(int dataRowIndex, PreparedStatementParams rsStr) throws ApplicationException{
		SqlSession session = null;
		Connection connection = null;
		ResultSet rs= null;
		PreparedStatement statement = null;
		SqlSessionFactory sqlSessionFactory = Ccay.getContext().getBean("sqlSessionFactory",SqlSessionFactory.class);
		try {
			session = sqlSessionFactory.openSession();
			connection = session.getConnection();
			if(StringUtil.isNullOrEmpty(rsStr.getSql())){
				throw new ExcelExportException("ccay.excel.00110010");
			}
			statement =  connection.prepareStatement(rsStr.getSql());
			log.debug("ResultSet:"+rsStr.getSql());
			int i = 1;
			for(Object o:rsStr.getParameter()){
					statement.setObject(i, o);
					log.debug("ResultSet Parameter["+i+"]:"+o);
				i++;
			}
			rs = statement.executeQuery();
			return reportWriter.writeCellData(dataRowIndex, fieldNames, rs,maxTotalNum,this.fieldProcessor);
		}catch (SQLException e) {
			log.error(e);
			throw new ExcelExportException("ccay.excel.00110010",e);
		}finally{
			tryCloseStatement(statement);
			tryCloseResultSet(rs);
			tryCloseConnection(connection);
			tryCloseSqlSession(session);
		}
	}
	/**
	 * 按照普通list 导出数据
	 * @param dataRowIndex
	 * @param pageVO
	 * @param batchData
	 * @param dataProvider
	 * @return
	 * @throws ApplicationException
	 * Created：2013-4-1
	 * @author chaowangbang
	 */
	private  int writeCellDataByList(int dataRowIndex, PageVO pageVO,List<?> batchData,IExcelDataProvider dataProvider) throws ApplicationException
	{
		// 写入第一批数据
		dataRowIndex = writeCellData(dataRowIndex, batchData);

		// 迭代写入分页数据
		for (int i = 2; i <= pageVO.getTotalPages(); i++) {
			pageVO.setCurPage(i);
			// 查询分页数据
			batchData = dataProvider.getBatchData(this.conditionObject, pageVO);
			dataRowIndex = writeCellData(dataRowIndex, batchData);
		}
		// 返回当前sheet操作的行数
		return pageVO.getTotalRows();
	}
	/** 
	 * 尝试关闭
	 * @param stmt
	 */
	public void tryCloseResultSet(ResultSet rs) {
		if(rs != null){
			try{
				rs.close();
			}catch(SQLException e){
				rs = null;
				log.error(e);
			}
		}
	}
	/** 
	 * 尝试关闭
	 * @param stmt
	 */
	private void tryCloseStatement(Statement stmt) {
		if(stmt != null){
			try{
				stmt.close();
			}catch(SQLException e){
				stmt = null;
				log.error(e);
			}
		}
	}
	/** 
	 * 尝试关闭
	 * @param stmt
	 */
	private void tryCloseSqlSession(SqlSession session) {
		if(session != null){
				session.close();
		}
	}
	/** 
	 * 尝试关闭
	 * @param stmt
	 */
	private void tryCloseConnection(Connection conn) {
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				conn = null;
				log.error(e);
			}
		}
	}
	/**
	 * 检查是否需要使用数据源连接
	 * @param batchData
	 * @return
	 * Created：2013-3-30
	 * @author chaowangbang
	 */
	private boolean checkPreparedStatementParams(List<?> batchData){
		//如果返回是空的结果  不处理
		if(CollectionUtil.isNullOrEmpty(batchData)){
			return false;
		}
		//如果返回是大于一条记录的结果 不处理
		if(batchData.size()>1){
			return false;
		}
		//当返回的记录是一条 并且是PreparedStatementParams时  我们需要用PreparedStatementParams处理
		Object temp = batchData.get(0);
		if(PreparedStatementParams.class.getName().equals(temp.getClass().getName())){
			return true;
		}
		return false;
	}
	

	/**
	 * 判断要导出的总行数是否超过最大导出数
	 * 
	 * @author chaowangbang
	 * @since 2012-11-6
	 * @param currentSheetTotalNum
	 * @throws ExcelExportException
	 */
	private void checkMaxCount(int currentSheetTotalNum) throws ExcelExportException {
		if (currentSheetTotalNum + totalNum > maxTotalNum) {
			throw new ExcelExportException("ccay.excel.00110009", maxTotalNum);
		} else {
			totalNum += currentSheetTotalNum;
		}
	}

	/**
	 * 把批量的数据写入到excel文件
	 * 
	 * @param dataRowIndex
	 * @param beachData
	 * @return
	 * @throws ApplicationException
	 */
	private int writeCellData(int dataRowIndex, List<?> batchData) throws ApplicationException {
		if (CollectionUtil.isNullOrEmpty(batchData)) {
			return dataRowIndex;
		}
		// 每次解析100条数据并且输出到excel文件
		int smallBatch = batchData.size() / 100;
		for (int i = 0; i < smallBatch; i++) {
			dataRowIndex = smallBatchWriteCellData(dataRowIndex, batchData.subList(i * 100, (i + 1) * 100));
		}
		dataRowIndex = smallBatchWriteCellData(dataRowIndex, batchData.subList(smallBatch * 100, batchData.size()));
		return dataRowIndex;
	}

	/**
	 * 把数据分批次 写入到excel文件
	 * @param dataRowIndex
	 * @param smallBatchData
	 * @return
	 * @throws ApplicationException
	 * @author chaowangbang
	 */
	private int smallBatchWriteCellData(int dataRowIndex, List<?> smallBatchData) throws ApplicationException {
		List<List<Object>> dataList = null;
		try {
			dataList = converterData(smallBatchData);
		} catch (ConvertException e) {
			log.error(e);
			throw new ExcelExportException(ExcelExportConstants.GET_VODATA_ERROR, e);
		}
		reportWriter.writeCellData(dataRowIndex, dataList,this.fieldProcessor);
		return dataRowIndex + dataList.size();
	}

	/**
	 * 把vo数据转化为普通的数据
	 * 
	 * @param beachData
	 * @return
	 * @throws ConvertException
	 */
	private List<List<Object>> converterData(List<?> batchData) throws ConvertException{
		List<List<Object>> dataList = new ArrayList<List<Object>>();
		for (Object o : batchData) {
			dataList.add(this.voConverter.reflect(o, this.fieldNames));
		}
		return dataList;
	}

	/**
	 * 输出excel文件
	 * 
	 * @throws ApplicationException
	 */
	public void excelExportFile() throws ApplicationException {

		this.reportWriter.excelExportFile();
	}

	/**
	 * 清除对象
	 */
	public void clearObject() {
		this.reportWriter.clearObject();
	}

	/**
	 * 获取文件大小
	 * 
	 * @return
	 */
	public long getFileSize() {
		return this.reportWriter.getFileSize();
	}

	/**
	 * 读取xml文件的映射关系把对象拆分出来，把数据对象类型使用map保存起来
	 */
	private void initColumnsConverter(IExcelExportSupport exportSupport) throws ApplicationException {
		// 处理字段类型
		this.dataType = new HashMap<Integer, String>();
		//处理列对应的bean,fieldProcessorMap
		this.fieldProcessor=new HashMap<String,IFieldProcessor>();
		// 处理属性名称
		this.fieldNames = new String[this.sheetMeta.getColumns().size()];
		// 处理导出excel标题
		this.excelTitles = new String[this.sheetMeta.getColumns().size()];
		// 处理导出excel标题批注
		this.excelAnnotations = new String[this.sheetMeta.getColumns().size()];
		
		String voBeanName = sheetMeta.getVoBeanName();
		Class<?> voClass = null;
		try {
			voClass = ClassUtils.getClass(voBeanName);
		} catch (ClassNotFoundException ex) {
			throw new ExcelExportException(ExcelExportConstants.VO_CLASS_NOT_FOUND, ex, voBeanName);
		}
		//改成普通的for()循环
		int i = 0;
		for (ColumnMeta columnMeta : this.sheetMeta.getColumns()) {

			this.fieldNames[i] = columnMeta.getFieldName();
			// 属性类如果在xml文件中有配置 那么使用配置类型 否则去当前VO中获取属性类
			if (StringUtil.isNullOrEmpty(columnMeta.getType())) {
				this.dataType.put(i, getFieldType4Class(voClass, columnMeta.getFieldName()));
			} else {
				this.dataType.put(i, columnMeta.getType());
			}
			if(!StringUtil.isNullOrEmpty(columnMeta.getExtentionBean())){
				IFieldProcessor fieldProcessor=exportSupport.getFieldProcessor(columnMeta.getExtentionBean());
				this.fieldProcessor.put(columnMeta.getDisplayName(),fieldProcessor );
			}
			this.excelTitles[i] = columnMeta.getDisplayName();
			this.excelAnnotations[i] = columnMeta.getAnnotation();
			i++;
		}
	}

	/**
	 * 返回属性的类型
	 * 
	 * @param voClass
	 * @param fieldName
	 * @return
	 */
	private static String getFieldType4Class(Class<?> voClass, String fieldName) throws ApplicationException {
		if (StringUtil.isNullOrEmpty(fieldName)) {
			// 如果字段名是空的 抛出异常
			throw new ExcelExportException(ExcelExportConstants.VO_CLASS_FIELD_TYPE, fieldName);
		}
		return wrapperType2String(getFieldClass(voClass, fieldName));
	}

	/**
	 * 迭代循环 查找对象的属性
	 * 
	 * @param voClass
	 * @param fieldName
	 * @return
	 * @throws ApplicationException
	 */
	private static Class<?> getFieldClass(Class<?> voClass, String fieldName) throws ApplicationException {
		if(voClass == null){
			throw new ExcelExportException(ExcelExportConstants.VO_CLASS_FIELD_TYPE, new BadArgException("voClass"));
		}
		if(StringUtil.isNullOrEmpty(fieldName)){
			throw new ExcelExportException(ExcelExportConstants.VO_CLASS_FIELD_TYPE, new BadArgException("fieldName"));
		}
		Class<?> whileClass = voClass;
		try {
			// 如果是a.b.c格式的属性，那么迭代获取最终对象class
			String[] propertyNames = fieldName.split("\\.");
			for (int i = 0; i < propertyNames.length; i++) {
				String propertyName = propertyNames[i];
				Field findField = ReflectionUtils.findField(whileClass, propertyName);
				if (findField == null) {
					throw new NoSuchFieldException("Can not found field [" + propertyName + "] in class : " + whileClass.getName());
				}
				whileClass = findField.getType();
			}
			return whileClass;
		} catch (SecurityException e) {
			throw new ExcelExportException(ExcelExportConstants.VO_CLASS_FIELD_TYPE, e, fieldName);
		} catch (NoSuchFieldException e) {
			throw new ExcelExportException(ExcelExportConstants.VO_CLASS_FIELD_TYPE, e, fieldName);
		}
	}

	/**
	 * 返回类型的字符串形式（类型的名字）
	 * 
	 * @param primitiveType
	 * @return
	 * @throws ApplicationException 
	 */
	private static String wrapperType2String(Class<?> primitiveType) throws ApplicationException {
		// 更具属性的类型 返回字符串的类型
		if(primitiveType == null){
			throw new ExcelExportException(ExcelExportConstants.VO_CLASS_NOT_FOUND, new BadArgException("primitiveType"));
		}
		return primitiveType.getSimpleName();
	}

}
